<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>snake</title>
</head>

<body>
    <script>
        class Snake {
            constructor() {
                this.step = 30 // 步进大小(缩放大小)
                this.initLen = 3 // 初始身体长度
                this.initData()
            }
            // 初始化游戏数据
            initData() {
                this.id = 'snakeid' + Date.now()
                this.fraction = 0 // 分数
                this.historyFraction = localStorage.getItem('historyFraction') // 历史最高分数
                this.currentDirection = 'd' // 当前方向
                this.autoSet = null // 自动移动定时器
                this.snakeBody = [] // 角色身体
                document.body.innerHTML = '' // 清空页面
                this.mapGame = document.createElement('div');
                this.sceneAdaptation()
                this.placeScene()
                this.createFood()
                this.createSnake()
                this.createUserMsg()
                this.createDialog()
                this.renderBody()
                this.initController()
            }
            // 根据设备适配最佳场景大小
            sceneAdaptation() {
                let w = window.innerWidth
                let h = window.innerHeight
                this.sW = ~~(w / this.step)// 场景宽度
                this.sH = ~~(h / this.step)// 场景高度
            }
            // 放置游戏场景
            placeScene() {
                this.setStyle(this.mapGame, {
                    width: this.sW,
                    height: this.sH,
                    top: '0',
                    left: '0',
                    position: 'absolute',
                    backgroundColor: '#121213',
                })
                document.body.appendChild(this.mapGame);
            }
            // 初始化控制器
            initController() {
                if (this.IsPC()) {
                    document.onkeypress = (e) => {
                        this.controllerMotin(e.key)
                    }
                } else {
                    let keyStyle = {
                        width: 2,
                        height: 2,
                        borderRadius: 0,
                        backgroundColor: "rgba(255,255,255,0.1)",
                        right: 0,
                        bottom: 0
                    }
                    let top = this.createDom(Object.assign(keyStyle, {
                        right: 2 * this.step + 'px',
                        bottom: 4 * this.step + 'px'
                    }))
                    top.onclick = () => { this.controllerMotin('w') }
                    let down = this.createDom(Object.assign(keyStyle, {
                        right: 2 * this.step + 'px',
                        bottom: 0
                    }))
                    down.onclick = () => { this.controllerMotin('s') }
                    let left = this.createDom(Object.assign(keyStyle, {
                        right: 4 * this.step + 'px',
                        bottom: 2 * this.step + 'px'
                    }))
                    left.onclick = () => { this.controllerMotin('a') }
                    let right = this.createDom(Object.assign(keyStyle, {
                        right: 0,
                        bottom: 2 * this.step + 'px'
                    }))
                    right.onclick = () => { this.controllerMotin('d') }
                    this.mapGame.appendChild(top);
                    this.mapGame.appendChild(down);
                    this.mapGame.appendChild(left);
                    this.mapGame.appendChild(right);
                }
            }
            // 控制器控制角色移动
            controllerMotin(key) {
                this.currentDirection = key
                this.motion(key)
                this.autoMotion()
            }
            // 创建食物
            createFood() {
                let food = document.getElementById('food' + this.id)
                if (food) {
                    this.mapGame.removeChild(food)
                }
                this.foodDiv = this.createDom({
                    borderRadius: '0',
                    top: ~~(Math.random() * this.sH),
                    left: ~~(Math.random() * this.sW)
                })
                this.foodDiv.id = 'food' + this.id
                this.mapGame.appendChild(this.foodDiv);
            }
            // 初始创建角色
            createSnake() {
                for (let i = 0; i < this.initLen; i++) {
                    let item = {
                        type: 'body',
                        time: Date.now()
                    }
                    item.dom = this.createDom({ top: 1, left: i })
                    // 头部
                    if (i == this.initLen - 1) {
                        item.type = 'head'
                        this.setStyle(item.dom, { backgroundColor: 'blue', border: '1px solid #F7D43D' })
                    }
                    this.snakeBody.push(item);
                }
            }
            // 角色移动操作
            motion(key) {
                // 判断弹窗是否打开
                if (this.dialog.style.display === 'block') return
                let len = this.snakeBody.length
                let head = this.snakeBody[len - 1]
                let { top, left } = this.getDomAddr(head.dom)
                const headDirection = {
                    w: { top: top - 1, left: left },
                    s: { top: top + 1, left: left },
                    a: { top: top, left: left - 1 },
                    d: { top: top, left: left + 1 },
                }
                // 接下来头要移动到的位置
                let nextArea = headDirection[key]
                // 边界判断
                if (!this.boundary(nextArea)) return
                // 吃到食物判断
                this.growUp(nextArea)
                // 同步身体位置
                this.snakeBody.forEach((item, index) => {
                    if (item.type === 'body') {
                        this.setStyle(item.dom, this.getDomAddr(this.snakeBody[index + 1].dom));
                    }
                })
                // 同步头的位置
                this.setStyle(head.dom, nextArea);
            }
            // 开启自动移动操作
            autoMotion() {
                if (!this.autoSet) {
                    let speed = this.fraction > 50 ? this.fraction / 40 : 1
                    this.autoSet = setInterval(() => {
                        this.motion(this.currentDirection)
                    }, 500 / speed)
                } else {
                    clearInterval(this.autoSet)
                    this.autoSet = null
                    this.autoMotion()
                }
            }
            // 关闭自动移动
            closeAutoMotion() {
                if (this.autoSet) {
                    clearInterval(this.autoSet)
                    this.autoSet = null
                }
            }
            // 判断吃到食物成长
            growUp(nextArea) {
                let { top, left } = this.getDomAddr(this.foodDiv)
                if (nextArea.top == top && nextArea.left == left) {
                    let newBody = {
                        type: 'body',
                        time: Date.now()
                    }
                    newBody.dom = this.createDom({
                        top: this.getDomAddr(this.foodDiv).top,
                        left: this.getDomAddr(this.foodDiv).left
                    })
                    this.snakeBody.unshift(newBody)
                    this.statistics()
                    this.renderBody()
                    this.createFood()
                }
            }
            // 边界判断
            boundary(nextArea) {
                if (nextArea.top < 0 || nextArea.left < 0 || nextArea.top >= this.sH || nextArea.left >= this.sW) {
                    this.gameOver()
                    return false
                }
                return true
            }
            // 游戏结束操作
            gameOver() {
                this.closeAutoMotion()
                const gradeList = [
                    { name: '绝世高手', fraction: 350 },
                    { name: '游戏熟手', fraction: 250 },
                    { name: '大众水平', fraction: 100 },
                    { name: '手残党', fraction: 0 }
                ]
                for (let item of gradeList) {
                    if (this.fraction >= item.fraction) {
                        this.grade = item.name
                        break;
                    }
                }
                // 保存游戏分数到缓存
                if (this.fraction > this.historyFraction) {
                    let historyFraction = document.getElementById('historyFraction' + this.id)
                    historyFraction.innerText = '最高分数：' + this.fraction
                    localStorage.setItem('historyFraction', this.fraction)
                }
                this.dialog.style.display = 'block'
                this.dialog.innerHTML = `<h2>GameOver</h2><p>你的段位是：${this.grade} </p><p>分数：${this.fraction} </p><p>点击重新开始</p>`
            }
            // 分数统计
            statistics() {
                this.fraction += ~~(Math.random() * this.snakeBody.length + 1)
            }
            // 创建游戏弹窗
            createDialog() {
                const that = this
                this.dialog = this.createDom({
                    width: this.sW * 0.4,
                    height: this.sW * 0.2,
                    zIndex: '300',
                    borderRadius: '20px',
                    top: '0',
                    left: '0',
                    right: '0',
                    bottom: '0',
                    margin: 'auto',
                    textAlign: 'center',
                    display: 'none',
                    backgroundColor: '#ffffff'
                })
                this.dialog.onclick = function () {
                    this.style.display = 'none'
                    that.closeAutoMotion()
                    that.initData()
                }
                this.mapGame.appendChild(this.dialog)
            }
            // 创建角色信息展示栏
            createUserMsg() {
                let msgStyle = {
                    width: 5,
                    height: 1,
                    zIndex: '200',
                    borderRadius: '0',
                    textAlign: 'center',
                    display: 'inline-block',
                    backgroundColor: 'transparent',
                    color: '#ffffff'
                }
                let fraction = this.createDom(msgStyle)
                let historyFraction = this.createDom(msgStyle)
                fraction.id = 'fraction' + this.id
                fraction.innerText = '分数：' + this.fraction
                historyFraction.id = 'historyFraction' + this.id
                historyFraction.innerText = '最高分数：' + (this.historyFraction || 0)
                this.setStyle(fraction, { left: 5 })
                this.mapGame.appendChild(historyFraction)
                this.mapGame.appendChild(fraction)
            }

            // 【工具函数】
            // 创建 dom
            createDom(style) {
                const options = {
                    width: 1,
                    height: 1,
                    zIndex: '100',
                    borderRadius: '50%',
                    position: 'absolute',
                    backgroundColor: this.randomColor(),
                }
                let dom = document.createElement('div')
                return this.setStyle(dom, Object.assign({}, options, style))
            }
            // 随机生成颜色
            randomColor() {
                const N = () => ~~(Math.random() * 200 + 50)
                return `rgb(${N()},${N()},${N()})`
            }
            // 设置 dom 样式
            setStyle(dom, style) {
                const address = ['width', 'height', 'top', 'left']
                for (let key in style) {
                    dom.style[key] = address.includes(key) ? style[key] * this.step + 'px' : style[key]
                }
                return dom
            }
            // 获取 dom 位置
            getDomAddr(dom) {
                let { top, left } = dom.style
                return { top: parseFloat(top) / this.step, left: parseFloat(left) / this.step }
            }
            // 渲染角色信息
            renderBody() {
                // 身体
                this.snakeBody.forEach(item => {
                    this.mapGame.appendChild(item.dom)
                })
                // 分数
                let fraction = document.getElementById('fraction' + this.id)
                fraction.innerText = '分数：' + this.fraction
            }
            // 判断是否为pc端
            IsPC() {
                var userAgentInfo = navigator.userAgent;
                var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod"];
                var flag = true;
                for (var v = 0; v < Agents.length; v++) {
                    if (userAgentInfo.indexOf(Agents[v]) > 0) {
                        flag = false;
                        break;
                    }
                }
                if (window.screen.width >= 768) {
                    flag = true;
                }
                return flag;
            }
        }

        const eatSnake = new Snake();
    </script>
</body>

</html>